<?php
// FIX due to lower than expected PHP version.
function strstr52($haystack, $needle, $before_needle=FALSE) {
 //Find position of $needle or abort
 if(($pos=strpos($haystack,$needle))===FALSE) return FALSE;

 if($before_needle) return substr($haystack,0,$pos+strlen($needle));
 else return substr($haystack,$pos);
}

/**
 * DonReY Framework 2012 :: Render Transform - DRY Template
 * Experimental minimalistic template system, with a clear symbol+keyword syntax, and programming language like content inclusion - with curly braces { }
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Modules/render.drytemplate.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Render
 */
class Drytemplate
{
	// Exception/Error codes
	const
		ERR_FILE_MISSING = 1
		;

	/**
	 * Template search paths (if they exists, template files are searched for first in these directories)
	 * @var array[string]
	 */
	private $tpl_paths = array();

	/**
	 * A quick method to render a template by passing information directly to the constructor (Optional, arguments are not necessary)
	 * Transform Constructor: Args array format
	 * [0] = unused, should be "Drytemplate"
	 * [1] = template or template file
	 * [2] = data to be used (usually a reference), but it might be absent
	 * [3] = mode: "template"/"file", defaults to "template"
	 * @param SimpleXMLElement $CFG
	 * @param array $args
	 */
	public function __construct($CFG, $args = array())
	{
		if(count($args) >= 4) {
			$mode = $args[3]; // mode
		} else
			$mode = 'template';
		if(count($args) >= 2) {
			if($mode == 'template')
				$this-> template = $args[1];
			if($mode == 'file')
				$this-> template_file = $args[1];
		}
		if(count($args) >= 3)
			$this-> rootdata = &$args[2];

		// setup template path ( for engines that use templates )
		if(isset($CFG-> paths)) {
			$this-> tpl_paths = $CFG-> paths;
		}
	}

	/**
	 * Scans a section of a string for doublequote-delimited zone to determine it's length
	 *
	 * @param string $string
	 * $param int $p Scan start location
	 * @return int End location of the string ( final quote )
	 */
	private function measure_String(&$string, $p)
	{
		$s = $p;
		$len = strlen($string);
		do {
			$p++;
			// end of string
			if($string[$p] == '"') {
				break;
			}
			// check for escaped quotes inside the string ( \" ), and skip them, also check for backslash itself ( \\ )
			if($string[$p] == '\\') {
				if($string[$p+1] == '"' || $string[$p+1] == '\\') {
					$p++;
				}
			}
			if($p >= $len-1)
				throw new MVC_Exception("Unterminated string");
		} while(1);

		return $p;
	}

	/**
	 * Scans the interior of a placeholder, which follows various syntax rules
	 *
	 * @param string $placeholder
	 */
	private function parse_Placeholder($placeholder, $modifiers=null)
	{
		if(strlen($placeholder) == 0)
			return null; // nothing to parse!

		$ph = new PH();
		// check if first character is special ( !, @, $ or # )
		switch($placeholder[0]) {
			// comment, the entire content of the placeholder is ignored, including any (useless) modifiers
			case '!':
				return null;
			// literal section, doesn't get parsed, but modifier processing does occur
			case '@':
				if(!is_null($modifiers)) {
					$ph-> mode = 'L';
					$ph-> str = substr($placeholder,1);
					break;
				} else
					return substr($placeholder,1);
			// $ represents the data itself - used when you don't want to search inside the data like for variables, but the data itself is what must appear
			// usually in conjunction with #use or #repeat
			case '$':
				$ph-> mode = 'SELF';
				break;
			// keyword, special meaning for each
			case '#':

				$kwd = explode(':',$placeholder,3);
				$ph-> mode = 'KW';
				$ph-> keyword = strtoupper(substr($kwd[0],1));

				// basic or dynamic variable
				$trec1 = array();
				$this-> parse_Pattern($kwd[1], $trec1);
				if(count($trec1) == 1 && is_string($trec1[0])) {
					// basic variable (no more curly brackets inside)
					$ph->_var = $trec1[0];
				} else {
					// dynamic variable
					$ph->_var = $trec1;
				}

				switch($ph-> keyword) {
					// the 3rd part doesn't exist
					case 'BREAK':
					case 'RAW':
						break;
					// the 3rd part is raw data, don't go into it ( similar to {@...}
					case 'EXP':
						$ph-> rawdata = $kwd[2];
						break;
					// the 3rd part is a pattern
					case 'USE':
					case 'UP':
					case 'ROOT':
					case 'IF':
					case 'IFNOT':
					case 'REPEAT':
					case 'STORE':
					case 'INCLUDE':
					case 'LOAD':
						$trec2 = array();
						$this-> parse_Pattern($kwd[2], $trec2);
						if(count($trec2) == 1 && is_string($trec2[0])) {
							$ph-> pattern = $trec2[0];
						} else {
							$ph-> pattern = $trec2;
						}
						break;
				}
				break;
			// basic or dynamic variable name
			default:
				$ph-> mode = 'VAR';

				// split into var and default
				$vd = explode(':',$placeholder,2);

				// parse the interior (for dynamic var names)
				$trec = array();
				$this-> parse_Pattern($vd[0], $trec);
				if(count($trec) == 1 && is_string($trec[0])) {
					// basic variable (no more curly brackets inside)
					$ph->_var = $trec[0];
				} else {
					// dynamic variable
					$ph->_var = $trec;
				}

				// check if default exists
				if(isset($vd[1])) {
					$def = array();
					$this-> parse_Pattern($vd[1], $def);
					if(count($def) == 1 && is_string($def[0])) {
						$ph->_default = $def[0];
					} else {
						$ph->_default = $def;
					}
				}
		}
		// modifiers
		if(!is_null($modifiers))
			$ph-> mods = $modifiers;
		return $ph;
	}

	/**
	 * Scans the interior of a modifiers section, which follows various syntax rules
	 * Returns the "modifiers" object which can be used by parse_Placeholder
	 *
	 * @param string $modifiers
	 */
	private function parse_Modifiers($modifiers)
	{
		if(strlen($modifiers) == 0)
			return null; // nothing to parse!

		// modifiers array
		$ma = array();

		$mstart = 0; $p = 0; $modo = new PH(); // modifier object
		$mode = 'kw';
		$len = strlen($modifiers);
		do {
			// detect strings inside the modifiers to skip them
			if($modifiers[$p] == '"') {
				$p = $this-> measure_String($modifiers, $p);
			}

			// start parameters detected
			if($modifiers[$p] == '(') {
				if($mode == 'params')
					throw new MVC_Exception("Bad modifier format: Open bracket encountered while expecting closed bracket");

				$modo-> kw = substr($modifiers,$mstart,$p-$mstart);

				$pstart = $p+1;
				$params = array();
				$mode = 'params';
			}

			// next parameter
			if($mode == 'params' && $modifiers[$p] == ',') {
				$param = substr($modifiers,$pstart,$p-$pstart);
				if($param && $param[0] == '"') // param is a string
					$param = stripslashes(substr($param,1,-1));
				$pstart = $p+1;
				$params[] = $param;
			}

			// end parameters detected, this is also end of modifier
			if($modifiers[$p] == ')') {
				if($mode!='params')
					throw new MVC_Exception("Bad modifier format: Closed bracket encountered without open bracket");

				// final parameter
				$param = substr($modifiers,$pstart,$p-$pstart);
				if($param && $param[0] == '"') // param is a string
					$param = stripslashes(substr($param,1,-1));
				$params[] = $param;

				// apply some type juggling on params
				$ptest = null;
				foreach($params as &$ptest) {
					// numeric
					if(is_numeric($ptest)) {
						if(strpos($ptest,'.')!==false) // check if float
							settype($ptest, 'float');
						elseif(strpos($ptest,'x')!==false) { // check if hex
							$ptest = hexdec($ptest);
						} else { // no, just your average int
							settype($ptest, 'int');
						}
					}
					// boolean literal
					if($ptest === 'true') {
						$ptest = true;
					}
					if($ptest === 'false')
						$ptest = false;
					// null
					if($ptest === 'null')
						$ptest = null;
				}

				$modo-> params = $params;
				$mode = 'endp';
			}

			// found the end of one modifier, record it
			if($modifiers[$p] == ';') {
				if($mode == 'params')
					throw new MVC_Exception("Bad modifier format: Semicolon encountered while expecting closed bracket");
				// mode is still "keyword, no paranthesis were encountered so far
				if($mode == 'kw')
					$modo-> kw = substr($modifiers,$mstart,$p-$mstart);

				$mode = 'kw';
				$mstart = $p+1;
				if($modo-> kw) // remove empty "modifiers"
					$ma[] = $modo;
				$modo = new PH();
			}
			$p++;
		} while($p < $len);
		// end encountered
		if($mode == 'params')
			throw new MVC_Exception("Bad modifier format: Tilde (end of parameters) encountered while expecting closed bracket");
		if($mode == 'kw') {
			$modo-> kw = substr($modifiers,$mstart,$p-$mstart);
		}
		if($modo-> kw) // remove empty "modifiers"
			$ma[] = $modo;

		return $ma;
	}

	/**
	 * Parses a template into it's components ( identifies all placeholders and other special elements, saves fragments.
	 * Calls iteself recursively for patterns inside placeholders.
	 *
	 * @param string $pattern Current pattern being analysed
	 * @param array $record Analysis recording location
	 */
	protected function parse_Pattern($pattern, &$record)
	{
		// Initialization: pattern position, length, where scanning has begin, test-depth inside curly brackets
		$p = 0; $len = strlen($pattern); $check_start = 0; $depth = 0;
		if($len == 0)
			return;

		// identify the start and stop of a {placeholder} . It might have other {}'s inside it
		do {
			// If we are inside a placeholder ( $depth > 0 ), {}'s might exist inside a string, so if a quote is detected, find the end of the string first
			if($depth > 0) {
				if($pattern[$p] == '"') {
					$p = $this-> measure_String($pattern, $p);
				}
			}
			// not inside brackets, not in string, find the matching end bracket
			if($pattern[$p] == '{') {
				if($depth == 0) {
					$begin = $p + 1;
				}
				$depth++;
			}
			// }'s are ignored if a placeholder hasn't been started
			if($depth > 0 && $pattern[$p] == '}') {
				$depth--;
				// bingo, here it is, analyse the placeholder
				if($depth == 0) {
					$end = $p;

					// first, record the plain-text fragment before the placeholder (only if it's length is > 0 )
					if($begin-$check_start-1 > 0)
						$record[] = substr($pattern,$check_start,$begin-$check_start-1);
					// move past the } character
					$p++;

					// after placeholders, a modifier section can appear ( which is delimited by two ~ ~ ), check if there is such item
					$modifier = null;
					if($p < $len-1 && $pattern[$p] == '~') { // yes !
						$mod_start = $p + 1;
						do {
							$p++;
							// check for strings inside modifiers ( they might contain ~ character, so it must be ignored )
							if($pattern[$p] == '"') {
								$p = $this-> measure_String($pattern, $p);
							}
							if($pattern[$p] == '~') { // found the end
								$modifier = $this-> parse_Modifiers(substr($pattern, $mod_start, $p-$mod_start));
								$p++;
								break;
							}
							if($p == $len-1)
								throw new MVC_Exception("Unterminated modifier section");
						} while(1);
					}
					// then, the placeholder ( which is an object )
					$ph = $this-> parse_Placeholder(substr($pattern,$begin,$end-$begin),$modifier);
					if(!is_null($ph))
						$record[] = $ph;
					// continue from after the placeholder
					$check_start = $p;
					// $p has already been advanced after the placeholder
					continue;
				}
			}
			$p++;
		} while($p < $len);

		// oops, trouble!
		if($depth > 0) {
			throw new MVC_Exception("Unterminated placeholder !");
		}
		// when we're done, record the last part of the string
		if($check_start < $len)
			$record[] = substr($pattern, $check_start);
	}

	/**
	 * Scans data for a value using the dot.syntax format
	 * It searches in array or objects, dynamically
	 *
	 * @param string $key Dot.syntax.formatted.data.key
	 * @param mixed $data Data to be searched
	 * @param string $default Default text for when data is not found (or unsearchable)
	 */
	private function scan_Data($key, &$data, &$updata = null, $default='')
	{
		if(strlen($key)==0) // no key, return data as is
			return $data;

		// ^ caret in the beginning of a key name forces the data to be searched in $rootdata instead of the current location.
		// Useful when location has been overridden by #repeat or #use and we want to access outside it.
		// Ignores the passed $data
		if($key[0] == '%') {
			$key = substr($key, 1);
			$data = &$this-> rootdata;
		} elseif($key[0] == '^' && !is_null($updata)) {
			$key = substr($key, 1);
			$data = &$updata;
		}

		if(empty($data))
			return null; // ... nothing to search for, nothing to return

		// search in array|object the proper key (recursive)
		$m = explode('.',$key);
		foreach($m as $segment) {

			if(is_object($data)) {
				if(isset($data-> $segment))
					$data = &$data-> $segment;
				else // not found !
					return $default; // the optional default value when data is not found
			} elseif(is_array($data)) {
				if(isset($data[$segment]))
					$data = &$data[$segment];
				else
					return $default;
			} else
				return $default; // we still have segments to search, but data is something else, not object, not array - does not contain keys (or member names)
		}

		return $data; // all segments processed, the searched data was found.
	}

	/**
	 * Little helper function for all keywords that need a "condition"
	 *
	 * @param string $varname
	 * @param mixed $data
	 * @return bool
	 */
	private function check_Condition($varname, &$data, &$updata = null)
	{
		$condition = $this-> scan_Data($varname, $data, $updata);
		if(is_array($condition) || is_object($condition))
			$condition = count($condition)>0;
		elseif(is_string($condition))
			$condition = strlen($condition)>0;
		else
			settype($condition, 'boolean');
		return $condition;
	}

	/**
	 * Modifies a placeholder's output according to modifier rules
	 *
	 * @param string $s
	 * @param array $modifiers
	 */
	private function modify_String($s, $modifiers)
	{
		// all mods are processed in order from left to right, passing the output forward in the chain
		foreach($modifiers as $mod) {
			switch($mod-> kw) {

				// simple modifiers
				case 'uppercase':
					$s = strtoupper($s);
					break;
				case 'lowercase':
					$s = strtolower($s);
					break;
				case 'capfirst':
					$s = ucfirst($s);
					break;
				case 'capitalize':
					$s = ucwords($s);
					break;
				case 'nl2br':
					$s = nl2br($s);
					break;
				case 'addslashes':
					$s = addslashes($s);
					break;
				case 'stripslashes':
					$s = stripslashes($s);
					break;
				case 'md5':
					$s = md5($s);
					break;
				case 'sha1':
					$s = sha1($s);
					break;
				case 'length':
					$s = strlen($s);
					break;
				case 'reverse':
					$s = strrev($s);
					break;
				case 'striptags':
					$s = strip_tags($s);
					break;
				case 'urlencode':
					$s = urlencode($s);
					break;
				case 'urldecode':
					$s = urldecode($s);
					break;
				case 'htmlentities':
					$s = htmlentities($s, ENT_QUOTES, 'UTF-8', false); // pre 5.4.0 needs the parameters
					break;
				case 'htmlspecialchars':
					$s = htmlspecialchars($s, ENT_QUOTES, 'UTF-8', false); // pre 5.4.0 needs the parameters
					break;
				case 'htmldecodeentities':
					$s = html_entity_decode($s, ENT_QUOTES, 'UTF-8');
					break;
					// cleans all spaces between html tags, glueing them together. Leaves all spaces inside a html tag intact
				case 'htmlstripspaces':
					// umm this looks ugly, no idea how it actually works... Thank you Google :)
					$s = preg_replace('/(?:(?<=\>)|(?<=\/\>))(\s+)(?=\<\/?)/', '', $s);
					break;

				// complex modifiers  // TODO: Parameters
				case 'trim':
					$s = trim($s);
					break;

				// wordwraps the string
				case 'wordwrap':
					if(isset($mod-> params)) {
						if(count($mod-> params) == 2)
							$s = wordwrap($s, (int)$mod-> params[0], (string)$mod-> params[1]);
						elseif(count($mod-> params) == 1)
							$s = wordwrap($s, (int)$mod-> params[0]);
					} else
						$s = wordwrap($s);
					break;

				// number format
				case 'numberformat':
					if(isset($mod-> params)) {
						if(count($mod-> params) == 3)
							$s = number_format($s, (int)$mod-> params[0], (string)$mod-> params[1], (string)$mod-> params[2]);
						elseif(count($mod-> params) == 2)
							$s = number_format($s, (int)$mod-> params[0], (string)$mod-> params[1]);
						elseif(count($mod-> params) == 1)
							$s = number_format($s, (int)$mod-> params[0]);
					} else
						$s = number_format($s);
					break;

				// cuts a fragment of the string (from beginning or arbitrary point). Can also add a tail to the cut string, but only if the string was actually cut.
				case 'cut':
					if(isset($mod-> params)) {
						if(count($mod-> params) == 3) {
							// all features
							$initial_len = strlen($s);
							$s = substr($s, (int)$mod-> params[1], (int)$mod-> params[0]);
							if($mod-> params[0]+$mod-> params[1] < $initial_len)
								$s.= (string)$mod-> params[2];
						} elseif(count($mod-> params) == 2) {
							if(is_numeric($mod-> params[1])) {
								// second param is start position
								$s = substr($s, (int)$mod-> params[1], (int)$mod-> params[0]);
							} else {
								// second param is cut completion
								$initial_len = strlen($s);
								$s = substr($s, 0, (int)$mod-> params[0]);
								if($mod-> params[0] < $initial_len)
									$s.= (string)$mod-> params[1];
							}
						} elseif(count($mod-> params) == 1)
							$s = substr($s, 0, (int)$mod-> params[0]);
					}
					// no effect without parameters
					break;

				// converts a recognizable date (strtotime) into a formatted date
				case 'date':
					$unixtime = strtotime($s);
					$s = date($mod-> params[0]?$mod-> params[0]:DATE_ATOM, $unixtime);
					break;
			}
		}
		return $s;
	}

	/**
	 * Processes parsed template (which is now an array of fragments and objects) and returns a string
	 *
	 * @param array $ptpl Parsed TemPLate
	 * @param mixed $data The current scope
	 * @param mixed $updata The previous scope (for keywords that modify the scope, like #use, #repeat, #root)
	 */
	protected function process_Parsed($ptpl, &$data, &$updata = null)
	{
		$dataref = null; $s = '';
		if(is_string($ptpl)) // ?
			$s = $ptpl;
		else

		foreach($ptpl as $fragment) {
			if(is_string($fragment)) // plain text, no processing, no replacing
				$s.= $fragment;
			else {
				// if ->_var exist, assamble it
				if(isset($fragment->_var))
					if(is_string($fragment->_var)) {
						// simple var (dot syntax, use the data scanner to locate it
						$varname = $fragment->_var;
					} else {
						// dynamic var (has subpatterns)
						$varname = $this-> process_Parsed($fragment->_var, $data, $updata);
					}
				// else, reset it (clear last iteration)
				else
					$varname = '';

				// here comes the fun part, it depends on what kind of placeholder
				switch($fragment-> mode) {

					// Literal
					case 'L':
						$str = (string)$fragment-> str;

						if(isset($fragment-> mods))
							$s.= $this-> modify_String($str, $fragment-> mods);
						else
							$s.= $str;
						break;

					// data itself
					case 'SELF':
						$s.= (string)$data;

						break;
					// search variable in data
					case 'VAR':

						// grab or process default
						if(isset($fragment->_default)) {
							if(is_string($fragment->_default))
								$default = $fragment->_default;
							else
								$default = $this-> process_Parsed($fragment->_default, $data, $updata);
						} else
							$default = '';


						// ~ tilde in the beginning of a variable name forces the content of a varible to behave like a pattern ( and get run through the parser )
						// Note: When used along the ^ modifier, they must appear in the order: ~^ ( ^~ won't work )
						if($varname[0] == '~') {
							$varname = substr($varname, 1);
							$patternmode = true;
						} else
							$patternmode = false;

						$varcontent = (string)$this-> scan_Data($varname, $data, $updata, $default); // convert found data to (string)

						if($patternmode) { // not that this cannot be pre-parsed, it's like PHP's eval()
							$pa = array();
							$this-> parse_Pattern($varcontent, $pa);
							$varcontent = $this-> process_Parsed($pa, $data, $updata);
						}

						if(isset($fragment-> mods))
							$s.= $this-> modify_String($varcontent, $fragment-> mods);
						else
							$s.= $varcontent;
						break;
					// Keyword
					case 'KW':
						switch($fragment-> keyword) {

							// {#use:var:pattern}
							case 'USE':
								$usedata = $this-> scan_Data($varname, $data, $updata);
								$str = $this-> process_Parsed($fragment-> pattern, $usedata, $data); // use data as is, this keyword needs it.

								if(isset($fragment-> mods))
									$s.= $this-> modify_String($str, $fragment-> mods);
								else
									$s.= $str;
								break;

							case 'ROOT':
								$usedata = $this-> scan_Data($varname, $this-> rootdata, $updata);
								$str = $this-> process_Parsed($fragment-> pattern, $usedata, $data); // use data as is, this keyword needs it.

								if(isset($fragment-> mods))
									$s.= $this-> modify_String($str, $fragment-> mods);
								else
									$s.= $str;
								break;

							case 'UP':
								$usedata = $this-> scan_Data($varname, $updata);
								$str = $this-> process_Parsed($fragment-> pattern, $usedata, $data); // use data as is, this keyword needs it.

								if(isset($fragment-> mods))
									$s.= $this-> modify_String($str, $fragment-> mods);
								else
									$s.= $str;
								break;

							// {#use:condition:pattern}
							case 'IF':
								if($this-> check_Condition($varname, $data, $updata)) {
									$str = $this-> process_Parsed($fragment-> pattern, $data, $updata);

									if(isset($fragment-> mods))
										$s.= $this-> modify_String($str, $fragment-> mods);
									else
										$s.= $str;
								}
								break;

							// {#ifnot:condition:pattern}
							case 'IFNOT':
								// condition variable analysed, now test it
								if(!$this-> check_Condition($varname, $data, $updata)) {
									$str = $this-> process_Parsed($fragment-> pattern, $data, $updata);

									if(isset($fragment-> mods))
										$s.= $this-> modify_String($str, $fragment-> mods);
									else
										$s.= $str;
								}
								break;

							// {#repeat:iterative-var:pattern}
							case 'REPEAT':
								$usedata = $this-> scan_Data($varname, $data, $updata);

								if(is_null($usedata))
									break;
								if(is_scalar($usedata) || is_resource($usedata)) {
									// error, attempt to use non-iterative data in a REPEAT, consider as just "one", and skip the foreach
									$str = $this-> process_Parsed($fragment-> pattern, $usedata, $data);
									if(isset($fragment-> mods))
										$s.= $this-> modify_String($str, $fragment-> mods);
									else
										$s.= $str;
								} else {
									// data is iterable (or it should be)
									foreach($usedata as $usedata_key=> $usedata_element) {
										$processed_element = $this-> process_Parsed($fragment-> pattern, $usedata_element, $data);
										if(($before_break = strstr52($processed_element,'%%BREAK%%',true))!==false) {
											$str = $before_break;
											break;
										} else
											$str = $processed_element;
										// modification is applied to each iteration individually, not as a whole (for example, cutting affects the iteration, not the full string resulting from repeat)
										if(isset($fragment-> mods))
											$s.= $this-> modify_String($str, $fragment-> mods);
										else
											$s.= $str;
									}
								}
								break;

							// {#break:condition}
							case 'BREAK':
								// if condition is true, returns a special notification to the above level (which should only be a #repeat keyword!)
								if($this-> check_Condition($varname, $data))
									$s.= '%%BREAK%%';
								break;

							// {#store:key:pattern}
							case 'STORE':
								// use a bit of reference magic :)
								unset($dataref);

								if(strlen($varname)>0) { // search only if there is a key to search for

									if($varname[0] == '%') {
										$varname = substr($varname, 1);
										$dataref = &$this-> rootdata;
									} elseif($varname[0] == '^') {
										$dataref = &$updata;
									} else
										$dataref = &$data;

									// search in array|object the proper key (recursive)
									$m = explode('.',$varname);
									foreach($m as $segment) {
										if(is_object($dataref)) {
											// when not found, create it (object) - Note that creation replicates the previous container type and keeps replicating it deeper
											if(!isset($dataref-> $segment))
												$dataref-> $segment = new stdClass();
											$dataref = &$dataref-> $segment;
										} elseif(is_array($dataref)) {
											// when not found, create it (array) - So when starting with array, we'll end up with arrays in arrays, if starting with objects, we end up with objects in objects
											if(!isset($dataref[$segment]))
												$dataref[$segment] = array();
											$dataref = &$dataref[$segment];
										} else
											break; // we still have segments to search, but data is something else, not object, not array - does not contain keys (or member names)
									}
								}
								// ... and update inside the source data
								$str = $this-> process_Parsed($fragment-> pattern, $data, $updata);

								if(isset($fragment-> mods))
									$dataref = $this-> modify_String($str, $fragment-> mods);
								else
									$dataref = $str;
								break;

							// {#include:data:filename} - Same as with {#use}, but loads the pattern from a file instead of using the inside content
							case 'INCLUDE':
								$filename = $this-> process_Parsed($fragment-> pattern, $data, $updata);

								// use references to allow the included template to modify data
								// unfortunately I haven't found a way to put this into a method, so it's a copy of the above code ( for #store )
								unset($dataref);
								if(strlen($varname)>0) { // search only if there is a key to search for

									if($varname[0] == '%') {
										$varname = substr($varname, 1);
										$dataref = &$this-> rootdata;
									} elseif($varname[0] == '^') {
										$dataref = &$updata;
									} else
										$dataref = &$data;

									// search in array|object the proper key (recursive)
									$m = explode('.',$varname);
									foreach($m as $segment) {
										if(is_object($dataref)) {
											// when not found, create it (object)
											if(!isset($dataref-> $segment))
												$dataref-> $segment = new stdClass();
											$dataref = &$dataref-> $segment;
										} elseif(is_array($dataref)) {
											// when not found, create it (array)
											if(!isset($dataref[$segment]))
												$dataref[$segment] = array();
											$dataref = &$dataref[$segment];
										} else
											break; // we still have segments to search, but data is something else, not object, not array - does not contain keys (or member names)
									}
								}

								$str = $this-> render_File($filename, $dataref, false); // do not save root for included files !

								if(isset($fragment-> mods))
									$s.= $this-> modify_String($str, $fragment-> mods);
								else
									$s.= $str;
								break;

							// {#RAW:filename} - Loads a file and puts it where the placeholder is without parsing the inside (still, modifiers can apply!)
							case 'RAW':
								$filename = $this-> process_Parsed($fragment->_var, $data, $updata);

								$contents = null;
								if(file_exists($filename)) {
									$contents = file_get_contents($filename);
								} else {
									// nope. Try in predefined paths
									if(isset($this-> tpl_paths['templates']))
										foreach($this-> tpl_paths['templates'] as $path) {
										if(file_exists($path.'/'.$filename)) {
											$contents = file_get_contents($path.'/'.$filename);
										}
									}
								}

								if(isset($fragment-> mods))
									$s.= $this-> modify_String($contents, $fragment-> mods);
								else
									$s.= $contents;
								break;

							// {#load:data:filename} - Loads a pattern from a file into the "data", without interpreting it - A combination of {#store} and {#raw}
							case 'LOAD':
								$filename = $this-> process_Parsed($fragment-> pattern, $data, $updata);

								$contents = null;
								if(file_exists($filename)) {
									$contents = file_get_contents($filename);
								} else {
									// nope. Try in predefined paths
									if(isset($this-> tpl_paths['templates']))
										foreach($this-> tpl_paths['templates'] as $path) {
										if(file_exists($path.'/'.$filename)) {
											$contents = file_get_contents($path.'/'.$filename);
										}
									}
								}

								if(!is_null($contents)) {

									// the store part
									// references are creepy. Because a function cannot return a reference to a reference (uh?), I can't do it with functions, so... 3 copies of the same stuff.
									unset($dataref);
									if(strlen($varname)>0) { // search only if there is a key to search for

										if($varname[0] == '%') {
											$varname = substr($varname, 1);
											$dataref = &$this-> rootdata;
										} elseif($varname[0] == '^') {
											$dataref = &$updata;
										} else
											$dataref = &$data;

										// search in array|object the proper key (recursive)
										$m = explode('.',$varname);
										foreach($m as $segment) {
											if(is_object($dataref)) {
												// when not found, create it (object)
												if(!isset($dataref-> $segment))
													$dataref-> $segment = new stdClass();
												$dataref = &$dataref-> $segment;
											} elseif(is_array($dataref)) {
												// when not found, create it (array)
												if(!isset($dataref[$segment]))
													$dataref[$segment] = array();
												$dataref = &$dataref[$segment];
											} else
												break; // we still have segments to search, but data is something else, not object, not array - does not contain keys (or member names)
										}

									}
									// file loading into the store location
									if(isset($fragment-> mods))
										$dataref = $this-> modify_String($contents, $fragment-> mods);
									else
										$dataref = $contents;

								} else
									break; // TODO: file not found, shoot a warning in the debug system (for now just break, nothing to store)

								break;

						}
						break;
				}

			}
		}
		return $s;
	}

	/**
	 * Attempts to load the contents of a file by searching for it's name directly or inside the template paths
	 * @param string $filename
	 */
	private function load_File($filename)
	{
		// nothing to return if no filename was specified
		if(!$filename)
			return false;
		// Try directly as filename first
		if(file_exists($filename)) {
			$this-> template = file_get_contents($filename);
		} else {
			// nope. Try in predefined paths
			if(isset($this-> tpl_paths['templates']))
			foreach($this-> tpl_paths['templates'] as $path) {
				if(file_exists($path.'/'.$filename)) {
					$this-> template = file_get_contents($path.'/'.$filename);
					return true;
				}
			}
		}
		// template file not found
		return false;
	}

	/**
	 * Stored template or reference (or last template used - it can be reused with different data)
	 * Also contains content of a template file
	 * @var string
	 */
	protected $template;

	/**
	 * Stored name of template file (or last template file used)
	 * @var string
	 */
	protected $template_file;

	/**
	 * Last used data or reference (can be reused with different template)
	 * The root data when using the template parser/processor. Also used by the {#root} keyword
	 * @var mixed
	 */
	protected $rootdata;

	/**
	 * Applies transformation engine on a string that contains the template (can reuse existing template and/or data)
	 * @param string $template Contents of a template
	 * @param mixed $data
	 */
	public function render_String($template=null, &$data=null)
	{
		if(!is_null($template))
			$this-> template = $template;

		if(!is_null($data))
			$this-> rootdata = &$data;

		// The magic starts here.

		if(is_null($this-> template))
			return '';

		$pa = array();
		$this-> parse_Pattern($this-> template, $pa);

		return $this-> process_Parsed($pa, $this-> rootdata);
	}

	/**
	 * Loads a file from the template directory then applies the transformation engine on it
	 * @param string $template_file Name of a template file
	 * @param mixed $data
	 */
	public function render_File($template_file=null, &$data=null)
	{
		if(!is_null($template_file))
			$this-> template_file = $template_file;

		if(!is_null($data))
			$this-> rootdata = &$data;

		// try to load the file
		if($this-> load_File($this-> template_file))
			return $this-> render_String();

		return '';
	}

	/**
	 * Resets internal data (so they don't get reused without passing new template/file/data)
	 */
	public function reset()
	{
		$this-> template = null;
		$this-> template_file = null;
		$this-> rootdata = null;
	}

	/**
	 * Arguments format
	 * [0] = template or template file
	 * [1] = data to be used (usually a reference), but it might be absent
	 * [2] = mode: "template"/"file", defaults to "template"
	 * @see DRY\Render\Transform_base::set_Data()
	 */
	public function set_Data()
	{
		$args = func_get_args();
		if(count($args) >= 3) {
			$mode = $args[2]; // mode
		} else
			$mode = 'template';
		if(count($args) >= 1) {
			if($mode == 'template') {
				$this-> template = $args[0];
				$this-> template_file = null;
			} if($mode == 'file') {
				$this-> template_file = $args[0];
				$this-> template = null; // clear "template" cache when asked to load another file
			}
		}
		if(count($args) >= 2 && !empty($args[1]))
			$this-> rootdata = &$args[1];
	}

	/**
	 * @see DRY\Render\Transform_base::__toString()
	 */
	public function __toString()
	{
		// __toString automatically uses the cached template string if it exists, then tries to load a template file
		if(!is_null($this-> template))
			return $this-> render_String();
		elseif(!is_null($this-> template_file))
			return $this-> render_File();
		else
			return '';
	}

}

/**
 * PlaceHolder helper class (will be used by the caching system)
 *
 * @author Wavetrex <wavetrex@gmail.com>
 */
class PH {

	/**
	 * Not that "magic" method
	 * @param array $arr
	 */
	public static function __set_state($arr)
    {
		return (object)$arr;
    }

}